﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Management;
using System.Windows.Threading;
using System.Threading;
using Neurokinetic.NeuroSkyNet.Model;
using System.Collections.ObjectModel;

namespace Neurokinetic.NeuroSkyNet.Controller
{
    public static class MindSetController
    {
        /// <summary>
        /// 
        /// </summary>
        public static MindSetModeType Mode
        {
            get;
            set;
        }
        /// <summary>
        /// Controls metronomic data retrieval from MindSet
        /// </summary>
        private static DispatcherTimer MindSetsTimer = new DispatcherTimer();

        /// <summary>
        /// On Data Updated Events to fire
        /// MindSetUpdateEventHandler(MindSetEventArgs mindSetEvent)
        /// </summary>
        public static event MindSetDataEventHandler OnMindSetDataUpdated;

        /// <summary>
        /// On Data Updated Events to fire
        /// MindSetStatusEventHandler(MindSetEventArgs mindSetEvent, MindSetStatusType status)
        /// </summary>
        public static event MindSetStatusEventHandler OnMindSetStatusUpdated;

        /// <summary>
        /// Callback method for data retrieval
        /// </summary>
        /// <returns></returns>
        public delegate void MindSetDataEventHandler(Object sender, MindSetEventArgs e);

        /// <summary>
        /// Callback method for MindSet status changes
        /// </summary>
        /// <returns></returns>
        public delegate void MindSetStatusEventHandler(Object sender, MindSetStatusEventArgs e);

        /// <summary>
        /// Public reference to the list of MindSets attached to computer
        /// This initiates a search for MindSets if it is null 
        /// </summary>
        public static ObservableCollection<MindSetModel> MindSets
        {
            get;
            private set;
        }

        /// <summary>
        /// Extract unmanaged thinkgear assembly
        /// </summary>
        static MindSetController()
        {
            if (!System.IO.File.Exists("ThinkGear.dll"))
            {
                String embeddedAssemblyName = Array.Find<String>(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames(), t => t.IndexOf("thinkgear.dll") >= 0);
                using (System.IO.Stream embeddedAssembly = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedAssemblyName))
                using (System.IO.FileStream assemblyAsFile = new System.IO.FileStream("ThinkGear.dll", System.IO.FileMode.Create))
                {
                    Byte[] fileBytes = new Byte[embeddedAssembly.Length];
                    embeddedAssembly.Read(fileBytes, 0, (Int32)fileBytes.Length);
                    assemblyAsFile.Write(fileBytes, 0, (Int32)fileBytes.Length);
                }
            }
        }
        /// <summary>
        /// This will attempt to find any MindSets attached to the computer 
        /// After attempting to locate and connect to MindSets will execute connectionHandler
        /// </summary>
        public static void SearchForMindsets(MindSetStatusEventHandler statusEventHandler, MindSetDataEventHandler dataEventHandler)
        {
            OnMindSetDataUpdated += dataEventHandler;
            SearchForMindsets(statusEventHandler);
        }
        /// <summary>
        /// This will attempt to find any MindSets attached to the computer 
        /// After attempting to locate and connect to MindSets will execute connectionHandler
        /// </summary>
        public static void SearchForMindsets(MindSetStatusEventHandler statusEventHandler)
        {
            OnMindSetStatusUpdated += statusEventHandler;
            SearchForMindsets();
        }
        /// <summary>
        /// This will attempt to find any MindSets attached to the computer 
        /// After attempting to locate and connect to MindSets will execute connectionHandler
        /// </summary>
        public static void SearchForMindsets()
        {
            Thread t = new Thread(DoSearch);
            t.Start();
        }
        /// <summary>
        /// This will attempt to find any MindSets attached to the computer 
        /// After attempting to locate and connect to MindSets will execute connectionHandler
        /// </summary>
        private static void DoSearch()
        {
            //Mindsets Search Started
            MindSetsTimer.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate()
            {
                OnMindSetStatusUpdated.Invoke(MindSetsTimer, new MindSetStatusEventArgs(null, MindSetStatusType.Searching));
            });

            if (MindSets == null)
                MindSets = new ObservableCollection<MindSetModel>();

            for (Int32 iSearch = 0; iSearch < 10; iSearch++)
            {
                ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_SerialPort WHERE Description LIKE '% Serial %' AND Description LIKE '% Bluetooth %' AND Availability=2 AND Status='OK'");
                foreach (ManagementObject queryObj in searcher.Get())
                {
                    var port = queryObj["DeviceID"].ToString();
                    if (MindSets != null)
                        foreach (var ms in MindSets)
                            if(ms.Port == port)
                                continue;

                    Int32 connectionId = ThinkGear.TG_GetNewConnectionId();
                    Int32 connectionStatus = ThinkGear.TG_Connect(connectionId, String.Format(@"\\.\{0}", port), ThinkGear.BAUD_57600, ThinkGear.STREAM_PACKETS);
                    if (connectionStatus == 0 && ThinkGear.TG_ReadPackets(connectionId, -1) > 0)
                    {
                        var foundMindSet = new Model.MindSetModel(connectionId, port);
                        MindSets.Add(foundMindSet);
                        if (Mode == MindSetModeType.RawWithLogs)
                        {
                            ThinkGear.TG_SetDataLog(connectionId, String.Format("DataLog{0}", DateTime.Now.Ticks));
                            ThinkGear.TG_SetStreamLog(connectionId, String.Format("StreamLog{0}", DateTime.Now.Ticks));
                        }

                        //Mindsets found Report the good news
                        MindSetsTimer.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate()
                        {
                            OnMindSetStatusUpdated.Invoke(MindSetsTimer, new MindSetStatusEventArgs(foundMindSet, MindSetStatusType.Connected));
                        });
                    }
                    else
                        ThinkGear.TG_FreeConnection(connectionId);
                }
                //If Mindset is found exit loop
                if (MindSets == null || MindSets.Count > 0)
                    break;

                //If unable to find Mindset wait 5 Seconds and try again
                Thread.Sleep(1000);
            }
            //Search has completed
            if (MindSets == null || MindSets.Count == 0)
            {
                //Stop the Metronome
                MindSetsTimer.Stop();

                //Mindsets not Report the bad news
                MindSetsTimer.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate()
                {
                    OnMindSetStatusUpdated.Invoke(MindSetsTimer, new MindSetStatusEventArgs(null, MindSetStatusType.UnableToConnect));
                });
            }
            else
            {
                //Start the Metronome Ticking for updates
                if (MindSetsTimer.Interval == default(TimeSpan))
                {
                    //This is the first time so the Tick event must be declared
                    MindSetsTimer.Tick += MindSetsTimer_Tick;
                    MindSetsTimer.Interval = new TimeSpan(0, 0, 0, 0, 30);
                }
                if (!MindSetsTimer.IsEnabled)
                    MindSetsTimer.Start();
            }
        }
        /// <summary>
        ///  Data retrieval action initiated by MindSetsTimer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MindSetsTimer_Tick(object sender, EventArgs e)
        {
            //If MindSets have been found populate data
            foreach (var mindSet in MindSets)
            {

                Int32 packetsRead = ThinkGear.TG_ReadPackets(mindSet.ConnectionID, -1);
                if (packetsRead <= 0)
                    continue;

                mindSet.SetPacketsRead(packetsRead);
                foreach (MindSetDataType dataType in Enum.GetValues(typeof(MindSetDataType)))
                    if (ThinkGear.TG_GetValueStatus(mindSet.ConnectionID, (Int32)dataType) != 0)
                        mindSet.Update(dataType, ThinkGear.TG_GetValue(mindSet.ConnectionID, (Int32)dataType));
                
                if (OnMindSetDataUpdated != null)
                MindSetsTimer.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, (Action)delegate()
                {
                    OnMindSetDataUpdated.Invoke(MindSetsTimer, new MindSetEventArgs(mindSet));
                });
            }
        }
        /// <summary>
        /// End connection to a MindSet and remove reference.
        /// </summary>
        public static void Disconnect()
        {
            if (MindSets != null && MindSets.Count > 0)
                for (Int32 i = 0; i < MindSets.Count; i++)
                    Disconnect(MindSets[i]);
        }

        /// <summary>
        /// End connection to a MindSet and remove reference.
        /// </summary>
        public static void Disconnect(MindSetModel mindSet)
        {
            if (MindSets != null && MindSets.Contains(mindSet))
                MindSets.Remove(mindSet);
            ThinkGear.TG_FreeConnection(mindSet.ConnectionID);
            mindSet = null;
        }
    }
}